Skip to content
This repository was archived by the owner on Apr 4, 2022. It is now read-only.

Implementation of the batch transanction view #1061

Merged
merged 36 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b9b81b7
Solve merge conflicts PR1057
henrypalacios Mar 18, 2022
82e6107
Show Trader address
henrypalacios Mar 18, 2022
d260f14
label styles
alongoni Mar 18, 2022
01d0681
Adding custom position layout
henrypalacios Mar 21, 2022
5813275
Adapt grid by batch of rows
henrypalacios Mar 21, 2022
f19e086
add basic tooltip
alongoni Mar 21, 2022
12dfa6e
Adding a tooltip
henrypalacios Mar 22, 2022
8da94f0
listed tap by mobile on tooltip
henrypalacios Mar 22, 2022
4b7a23d
add tooltip css
alongoni Mar 22, 2022
09e1b61
add eof space
alongoni Mar 22, 2022
fa90634
Adding amount of transfers
henrypalacios Mar 23, 2022
a97ca4d
Merge branch 'develop' into 1058-batch-viewer
henrypalacios Mar 23, 2022
ac3c703
css test deploy
alongoni Mar 23, 2022
de1ea88
bindPopper after data loading
henrypalacios Mar 23, 2022
d57199b
Merge branch '1058-batch-viewer' of github.com:gnosis/gp-ui into 1058…
henrypalacios Mar 23, 2022
4eec174
Changing css file name
henrypalacios Mar 23, 2022
29f3f1d
css arrow styles
alongoni Mar 23, 2022
c798546
Merge branch '1058-batch-viewer' of https://github.com/gnosis/gp-ui i…
alongoni Mar 23, 2022
90ddc55
css globakl styles BV
alongoni Mar 23, 2022
9410b4b
clean styles BV
alongoni Mar 23, 2022
10d363f
bigger CoW protocol logo
alongoni Mar 23, 2022
8ac7844
Remove all existing tooltips
henrypalacios Mar 23, 2022
1836ed4
Add comment for guidance
henrypalacios Mar 23, 2022
aed0005
add cow trader icon
alongoni Mar 25, 2022
6e804d3
cow trader icon with rays
alongoni Mar 25, 2022
4774a23
start moving colors to variables
henrypalacios Mar 25, 2022
908767d
replace colors from theme
alongoni Mar 25, 2022
75a77ce
Uppercase NetworkName and HighPrecission decimals
henrypalacios Mar 29, 2022
08af504
increase token name size
alongoni Mar 29, 2022
39e4c18
increase token name size
alongoni Mar 29, 2022
ff771bb
change tooltip events
alongoni Mar 29, 2022
0ade55f
Merge branch 'develop' into 1058-batch-viewer
henrypalacios Mar 30, 2022
05fa37a
Testing build
henrypalacios Mar 30, 2022
3899285
Limiting the max and min zoom view
henrypalacios Mar 31, 2022
fc9b41c
Resize graph view after press button: show transaction list
henrypalacios Apr 1, 2022
b33dbb4
Merge branch 'develop' into 1058-batch-viewer
mergify[bot] Apr 1, 2022
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
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
"bignumber.js": "^9.0.0",
"bn.js": "^4.11.9",
"combine-reducers": "^1.0.0",
"cytoscape": "^3.21.0",
"cytoscape-popper": "^2.0.0",
"date-fns": "^2.9.0",
"detect-browser": "^5.1.0",
"eth-json-rpc-middleware": "^4.4.1",
Expand All @@ -90,6 +92,7 @@
"qrcode.react": "^1.0.0",
"react": "^16.12.0",
"react-copy-to-clipboard": "^5.0.2",
"react-cytoscapejs": "^1.2.1",
"react-device-detect": "^1.15.0",
"react-dom": "^16.12.0",
"react-ga": "^3.3.0",
Expand Down Expand Up @@ -124,13 +127,15 @@
"@truffle/hdwallet-provider": "^1.2.0",
"@types/bn.js": "^4.11.6",
"@types/combine-reducers": "^1.0.0",
"@types/cytoscape": "^3.19.4",
"@types/enzyme": "^3.10.4",
"@types/enzyme-adapter-react-16": "^1.0.5",
"@types/hapi__joi": "^16.0.12",
"@types/jest": "^26.0.8",
"@types/node": "^14.0.14",
"@types/qrcode.react": "^1.0.0",
"@types/react-copy-to-clipboard": "^4.3.0",
"@types/react-cytoscapejs": "^1.2.2",
"@types/react-dom": "^16.9.5",
"@types/react-router": "^5.1.4",
"@types/react-router-dom": "^5.1.3",
Expand Down
2 changes: 1 addition & 1 deletion src/api/tenderly/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './tenderlyApi'

export { PublicTrade as Trade, Transfer, Account } from './types'
export type { PublicTrade as Trade, Transfer, Account } from './types'
35 changes: 21 additions & 14 deletions src/apps/explorer/components/TransactionsTableWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
import React, { useState, useEffect } from 'react'

import { BlockchainNetwork, TransactionsTableContext } from './context/TransactionsTableContext'
import { useGetTxOrders } from 'hooks/useGetOrders'
import { useGetTxOrders, useTxOrderExplorerLink } from 'hooks/useGetOrders'
import RedirectToSearch from 'components/RedirectToSearch'
import Spinner from 'components/common/Spinner'
import { RedirectToNetwork, useNetworkId } from 'state/network'
import { Order } from 'api/operator'
import { useTxOrderExplorerLink } from 'hooks/useGetOrders'
import { TransactionsTableWithData } from 'apps/explorer/components/TransactionsTableWidget/TransactionsTableWithData'
import { TabItemInterface } from 'components/common/Tabs/Tabs'
import ExplorerTabs from '../common/ExplorerTabs/ExplorerTab'
import styled from 'styled-components'
import { TitleAddress } from 'apps/explorer/pages/styled'
import { TitleAddress, FlexContainer, StyledTabLoader, BVButton, Title } from 'apps/explorer/pages/styled'
import { BlockExplorerLink } from 'components/common/BlockExplorerLink'
import { ConnectionStatus } from 'components/ConnectionStatus'
import { Notification } from 'components/Notification'
import { useTxBatchTrades } from 'hooks/useTxBatchTrades'
import { faListUl, faProjectDiagram } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import TransactionBatchGraph from 'apps/explorer/components/TransanctionBatchGraph'

interface Props {
txHash: string
networkId: BlockchainNetwork
transactions?: Order[]
}

const StyledTabLoader = styled.span`
padding-left: 4px;
`

const tabItems = (isLoadingOrders: boolean): TabItemInterface[] => {
return [
{
Expand All @@ -46,12 +43,11 @@ export const TransactionsTableWidget: React.FC<Props> = ({ txHash }) => {
const { orders, isLoading: isTxLoading, errorTxPresentInNetworkId, error } = useGetTxOrders(txHash)
const networkId = useNetworkId() || undefined
const [redirectTo, setRedirectTo] = useState(false)
const [batchViewer, setBatchViewer] = useState(false)
const txHashParams = { networkId, txHash }
const isZeroOrders = !!(orders && orders.length === 0)
const notGpv2ExplorerData = useTxOrderExplorerLink(txHash, isZeroOrders)
// TODO use on draw tx view
const txBatchTrades = useTxBatchTrades(networkId, txHash, orders && orders.length)
console.log('txBatchTrades', txBatchTrades)

// Avoid redirecting until another network is searched again
useEffect(() => {
Expand All @@ -75,15 +71,22 @@ export const TransactionsTableWidget: React.FC<Props> = ({ txHash }) => {
return <Spinner spin size="3x" />
}

const batchViewerButtonName = batchViewer ? 'Show Transactions list' : 'Show Batch Viewer'
const batchViewerButtonIcon = batchViewer ? faListUl : faProjectDiagram

return (
<>
<h1>
Transaction details
<FlexContainer>
<Title>Transaction details</Title>
<TitleAddress
textToCopy={txHash}
contentsToDisplay={<BlockExplorerLink type="tx" networkId={networkId} identifier={txHash} showLogo />}
/>
</h1>
<BVButton onClick={(): void => setBatchViewer(!batchViewer)}>
<FontAwesomeIcon icon={batchViewerButtonIcon} />
{batchViewerButtonName}
</BVButton>
</FlexContainer>
<ConnectionStatus />
{error && <Notification type={error.type} message={error.message} />}
<TransactionsTableContext.Provider
Expand All @@ -94,7 +97,11 @@ export const TransactionsTableWidget: React.FC<Props> = ({ txHash }) => {
isTxLoading,
}}
>
<ExplorerTabs tabItems={tabItems(isTxLoading)} />
{batchViewer ? (
<TransactionBatchGraph txBatchData={txBatchTrades} networkId={networkId} />
) : (
<ExplorerTabs tabItems={tabItems(isTxLoading)} />
)}
</TransactionsTableContext.Provider>
</>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { ElementDefinition } from 'cytoscape'
import { InfoTooltip, Node, TypeNodeOnTx } from './types'

export default class ElementsBuilder {
_center: ElementDefinition | null = null
_nodes: ElementDefinition[] = []
_edges: ElementDefinition[] = []
_SIZE: number
_countTypes: Map<string, number>

constructor(heighSize?: number) {
this._SIZE = heighSize || 600
this._countTypes = new Map()
}

_increaseCounType = (_type: string): void => {
const count = this._countTypes.get(_type) || 0
this._countTypes.set(_type, count + 1)
}
_createNodeElement = (node: Node, parent?: string): ElementDefinition => {
this._increaseCounType(node.type)
return {
group: 'nodes',
data: {
id: `${node.type}:${node.id}`,
label: node.entity.alias,
type: node.type,
parent: parent ? `${TypeNodeOnTx.NetworkNode}:${parent}` : undefined,
},
}
}

center(node: Node, parent?: string): this {
this._center = this._createNodeElement(node, parent)
return this
}

node(node: Node, parent?: string): this {
this._nodes.push(this._createNodeElement(node, parent))
return this
}

edge(
source: Pick<Node, 'type' | 'id'>,
target: Pick<Node, 'type' | 'id'>,
label: string,
tooltip?: InfoTooltip,
): this {
this._edges.push({
group: 'edges',
data: {
id: `${source.type}:${source.id}->${target.type}:${target.id}`,
source: `${source.type}:${source.id}`,
target: `${target.type}:${target.id}`,
label,
tooltip,
},
})
return this
}

build(customLayoutNodes?: CustomLayoutNodes): ElementDefinition[] {
if (!customLayoutNodes) {
return this._buildCoseLayout()
} else {
const { center, nodes } = customLayoutNodes
return [center, ...nodes, ...this._edges]
}
}

_buildCoseLayout(): ElementDefinition[] {
if (!this._center) {
throw new Error('Center node is required')
}
const center = {
...this._center,
position: { x: 0, y: 0 },
}
const nTypes = this._countTypes.size

const r = this._SIZE / nTypes - 100 // get radio

const nodes = this._nodes.map((node: ElementDefinition, index: number) => {
return {
...node,
position: {
x: r * Math.cos((nTypes * Math.PI * index) / this._nodes.length),
y: r * Math.sin((nTypes * Math.PI * index) / this._nodes.length),
},
}
})

return [center, ...nodes, ...this._edges]
}

getById(id: string): ElementDefinition | undefined {
// split <type>:<id> and find by <id>
if (this._center) {
return [this._center, ...this._nodes].find((node) => node.data.id?.split(':')[1] === id)
}

return this._nodes.find((node) => node.data.id?.split(':')[1] === id)
}
}

interface CustomLayoutNodes {
center: ElementDefinition
nodes: ElementDefinition[]
}

export function getGridPosition(type: TypeNodeOnTx, traderRowsLength: number, dexRowsLenght: number): number {
let col
const batchOf = 5
// Add a column for each batch of n
const leftPaddingCol = 1 + Math.round(traderRowsLength / batchOf)
const rightPaddingCol = leftPaddingCol + 1 + Math.round(dexRowsLenght / batchOf)

if (type === TypeNodeOnTx.Trader) {
col = 0 // first Column
} else if (type === TypeNodeOnTx.CowProtocol) {
col = leftPaddingCol
} else {
col = rightPaddingCol
}
return col
}

/**
* Build a grid layout using the 'position' attribute
* using 'x' for the columns and 'y' for the rows.
*/
export function buildGridLayout(
countTypes: Map<TypeNodeOnTx, number>,
center: ElementDefinition | null,
nodes: ElementDefinition[],
): { center: ElementDefinition; nodes: ElementDefinition[] } {
if (!center) {
throw new Error('Center node is required')
}

const maxRows = Math.max(...countTypes.values())
const middleOfTotalRows = Math.floor(maxRows / 2)
const traders = countTypes.get(TypeNodeOnTx.Trader) || 0
const dexes = countTypes.get(TypeNodeOnTx.Dex) || 0
const _center = {
...center,
position: { y: middleOfTotalRows, x: getGridPosition(center.data.type, traders, dexes) },
}

let counterRows = { [TypeNodeOnTx.Trader]: 0, [TypeNodeOnTx.Dex]: 0 }
if (traders > dexes) {
const difference = (traders - dexes) / 2
counterRows[TypeNodeOnTx.Dex] = Math.floor(difference)
} else if (traders < dexes) {
const difference = (dexes - traders) / 2
counterRows[TypeNodeOnTx.Trader] = Math.floor(difference)
}

const _nodes = nodes.map((node) => {
const _node = {
...node,
position: {
y: counterRows[node.data.type],
x: getGridPosition(node.data.type, traders, dexes),
},
}

if (node.data.type === TypeNodeOnTx.Trader) {
counterRows = { ...counterRows, [TypeNodeOnTx.Trader]: counterRows[TypeNodeOnTx.Trader] + 1 }
} else if (node.data.type === TypeNodeOnTx.Dex) {
counterRows = { ...counterRows, [TypeNodeOnTx.Dex]: counterRows[TypeNodeOnTx.Dex] + 1 }
}

return _node
})

return { center: _center, nodes: _nodes }
}
Loading